"""
This model can be used with pipes or queues.
"""

import os
import sys
import time
import random

import pygame
from pygame.locals import *

import multiproc
import spatialhash
from vec2d import Vec2d

WORLD_RECT = None

obj_ids = (i for i in xrange(sys.maxint))


class Sprite(object):
    
    size = 11,11
    
    def __init__(self, position):
        self.obj_id = obj_ids.next()
        
        self.rect = Rect((0,0),self.size)
        self._position = Vec2d(0,0)
        
        self.position = position
        choices = [-1,-0.75,-0.5,0.5,0.75,1]
        randchoice = random.choice
        self.step = Vec2d(randchoice(choices), randchoice(choices))
        self.hit = False
    
    @property
    def position(self):
        return self._position
    @position.setter
    def position(self, val):
        p = self._position
        p.x,p.y = val
        self.rect.center = round(p.x),round(p.y)
    
    def update(self, *args):
        self.hit = False
        self.position += self.step
        sr = self.rect
        wr = WORLD_RECT
        if not wr.contains(sr):
            if sr.x < wr.x or sr.right > wr.right:
                self.position -= (self.step.x,0)
                self.step.x = -self.step.x
            if sr.y < wr.y or sr.bottom > wr.bottom:
                self.position -= (0,self.step.y)
                self.step.y = -self.step.y


class _BaseState:
    @staticmethod
    def enter(self):
        self.log('{0}: entering {1}'.format(self.__class__.__name__, self.state.__name__))
    @staticmethod
    def update(self):
        pass
    @staticmethod
    def exit(self):
        self.log('{0}: exiting {1}'.format(self.__class__.__name__, self.state.__name__))
class NeedViewState(_BaseState):
    @staticmethod
    def update(self):
        if self.screen_rect:
            return BuildWorldState
class BuildWorldState(_BaseState):
    @staticmethod
    def update(self):
        self.create_sprites()
        return RunWorldState
class RunWorldState(_BaseState):
    @staticmethod
    def update(self):
        self.real_time = time.time()
        if self.real_time - self.last_tick >= self.clock_interval:
            self.game_time += self.clock_interval
            self.last_tick = self.real_time
            self.update_sprites()


class ModelQueueHandler(multiproc.QueueHandler):
    
    def msg_default(self, obj):
        self.runtime.log('ModelMsgHandler: unhandled: ' + str(obj))
    
    def msg_screen_rect(self, data):
        global WORLD_RECT
        self.runtime.log('ModelMsgHandler: screen_rect ' + str(data))
        WORLD_RECT = data
        self.runtime.screen_rect = data
        self.runtime.sprites = spatialhash.SpatialHash(data, data.w/8)
    
    def msg_spawn_sprite(self, pos):
        self.runtime.log('ModelMsgHandler: spawn_sprite')
        self.runtime.create_sprites()
    
    def msg_toggle_collisions(self):
        self.runtime.do_collisions = not self.runtime.do_collisions


class ModelRuntime(multiproc.Runtime):
    
    def _post_init(self):
        """This gets called by multiproc.Runtime.run after the process is forked.
        """
        self.log('Model: post init')
        # set SDL to use the dummy NULL video driver, 
        #   so it doesn't need a windowing system.
        os.environ['SDL_VIDEODRIVER'] = 'dummy'
        pygame.init()
        pygame.event.set_allowed(None)
        self.screen = pygame.display.set_mode((1,1))
        self.screen_rect = None
        self.sprites = None
        
        self.do_collisions = True
        
        self.real_time = 0.0
        self.game_time = 0.0
        self.last_tick = 0.0
        self.clock_interval = 30.0 / 1000.0
        
        self.state = _BaseState
        self.set_state(NeedViewState)
    
    def set_state(self, new_state):
        if self.state != new_state:
            self.state.exit(self)
            self.state = new_state
            self.state.enter(self)
    
    def update(self):
        new_state = self.state.update(self)
        if new_state:
            self.set_state(new_state)
    
    def update_sprites(self):
        sprites = self.sprites
        for s in sprites:
            s.update()
            sprites.add(s)
        if self.do_collisions:
            for s in sprites.collidealllist():
                s[0].hit = True
        data = [(s.obj_id,s.position,s.hit) for s in sprites]
        self.put('view', ('sprite_pos', data))
    
    def create_sprites(self):
        for i in xrange(25):
            self.new_sprite()
    
    def new_sprite(self):
        x = random.randrange(self.screen_rect.w)
        y = random.randrange(self.screen_rect.h)
        sprite = Sprite((x,y))
        sprite.position = sprite.rect.clamp(self.screen_rect).center
        self.sprites.add(sprite)
        self.put('view', ('new_sprite', (sprite.obj_id, sprite.position)))
